(function (fruit, dom, _, support, global, document, undefined) {
    var each = _.each;
    var position = fruit.position;
    var getHash = dom.identity;

    function contains(inElement, inTarget, isSelf) {
        if (!inTarget) return null;
        return inElement.contains ? (inElement != inTarget || isSelf) && inElement.contains(inTarget) : !!(inElement.compareDocumentPosition(inTarget) & 16);
    }

    fruit.define('fruit.Event', {
        methods:{
            init:function (inEvent) {
                if (inEvent) {
                    this.originalEvent = inEvent;
                    this.type = inEvent.type;
                    this.target = this.getTarget();
                }
            },
            getTarget:function () {
                var event = this.originalEvent;
                var target = event.target || event.srcElement || document;//thanks jquery
                //Mousewheel will apeared
                if (target.nodeType === 3) {
                    target = target.parentNode;
                }
                return target;
            },
            stopPropagation:function () {
                var event = this.originalEvent;
                if ('stopPropagation' in event) {
                    event.stopPropagation();
                } else {
                    event.cancelBubble = true;
                }
            },
            preventDefault:function () {
                var event = this.originalEvent;
                if ('preventDefault' in event) {
                    event.preventDefault();
                } else {
                    event.returnValue = false;
                }
            },
            stop:function () {
                this.stopPropagation();
                this.preventDefault();
            }
        }
    });
    fruit.define('fruit.UIEvent', fruit.Event, {
        methods:{
            init:function (inEvent) {
                if (inEvent) {
                    this.inherited(inEvent);
                }
            },
            getDocument:function () {
                return this.target.ownerDocument || document;
            },
            getRelatedTarget:function () {
                var event = this.originalEvent;
                var related;
                if ("relatedTarget" in event) {
                    related = event.relatedTarget
                } else {
                    if ('mouseover' == event.type) {
                        related = event.fromElement;
                    } else {
                        related = event.toElement;
                    }
                }
                return related
            }
        }
    });
    fruit.define('fruit.FocusEvent', fruit.UIEvent, {
        methods:{
            init:function (inEvent) {
                if (inEvent) {
                    this.inherited(inEvent);
                    if (!support.w3cFocus) {
                        var reverseEventHook = {
                            'focusin':'focus',
                            'focusout':'blur'
                        };
                        this.type = reverseEventHook[inEvent.type];
                    }
                }
            }
        }
    });
    fruit.define('fruit.MouseEvent', fruit.UIEvent, {
        methods:{
            getPageXY:function () {
                var doc = this.getDocument();
                var event = this.originalEvent;
                return {
                    x:event.pageX || (event.clientX + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - 2) || 0,
                    y:event.pageY || (event.clientY + (doc.documentElement.scrollTop || doc.body.scrollTop) - 2) || 0
                };
            },
            getOffsetXY:function () {
                var event = this.originalEvent;
                var offsetX, offsetY;
                if (('offsetX' in event) || ('offsetY' in event)) {
                    offsetX = event.offsetX;
                    offsetY = event.offsetY;
                } else {
                    var offset = position.getOffset(this.target);
                    var pageXY = this.getPageXY();
                    offsetX = pageXY.x - offset.left;
                    offsetY = pageXY.y - offset.top;
                }
                return {
                    x:offsetX,
                    y:offsetY
                };
            },
            getWhich:function () {
                var event = this.originalEvent;
                var button = event.button;
                //normalize ie to the w3c browser
                if (!event.which && button !== undefined) {
                    return  [ 0, 1, 3, 0, 2, 0, 0, 0 ][ button ];
                } else {
                    return event.which;
                }
            },
            getWheelData:function () {
                var event = this.originalEvent;
                var wheelData;
                if ('wheelDelta' in event) {
                    wheelData = Math.round(event.wheelDelta);
                }
                else if ('detail' in event) {
                    wheelData = -event.detail * 40;
                }
                return wheelData;
            }

        }
    });
    fruit.define('fruit.KeyboardEvent', fruit.UIEvent, {
        methods:{
            getKeyCode:function () {
                var event = this.originalEvent;
                return ('keyCode' in event) ? event.keyCode : (event.charCode || event.which || 0);
            },
            getKeyChar:function () {
                var keyCode = this.getKeyCode();
                return String.fromCharCode(keyCode);
            },
            hasSystemKey:function () {
                var event = this.originalEvent;
                return event.ctrlKey || event.altKey;
            }
        }
    });
    var EventHandler = fruit.define('fruit.event.EventHandler', {
        methods:{
            init:function (type, actualType, capture) {
                this._type = type;
                this._actualType = actualType || type;
                this._capture = capture || false;
                this._map = {};
                this._dispatch = _.bind(this.dispatch, this);
            },
            activate:function () {
                if (!this._active) {
                    eventEngine.addEventListener(document, this._actualType, this._dispatch, this._capture);
                    this._active = true;
                }
            },
            deactivate:function () {
                if (this._active) {
                    eventEngine.removeEventListener(document, this._actualType, this._dispatch, this._capture);
                    this._active = false;
                }
            },
            upon:function (owner, callback, context, meta) {
                var map = this._map;
                var hash = getHash(owner);
                var items = map[hash] = map[hash] || [null];
                items.owner = owner;
                if (callback) {
                    items[0] = {
                        owner:owner,
                        callback:callback,
                        context:context,
                        meta:meta
                    };
                }
                else {
                    items[0] = null;
                }
            },
            on:function (owner, callback, context, meta) {
                var map = this._map;
                var hash = getHash(owner);
                var items = map[hash] = map[hash] || [null];
                items.owner = owner;
                items.push({
                    owner:owner,
                    callback:callback,
                    context:context,
                    meta:meta
                });
            },
            off:function (owner, callback, context) {
                var map = this._map;
                var hash = getHash(owner);
                var items = map[hash];
                if (items) {
                    if (callback == null) {
                        items.length = 1;
                    }
                    else if (context == null) {
                        each(items.slice(), function (item, index) {
                            if (item.callback == callback) {
                                items.splice(index, 1);
                                return false;
                            }
                        });
                    }
                    else {
                        each(items.slice(), function (item, index) {
                            if (item.callback == callback && item.context == context) {
                                items.splice(index, 1);
                                return false;
                            }
                        });
                    }
                }
            },
            dispatch:function (event) {
                var payload = {};
                this._event = event;
                this.initPayload(payload);
                this.initTarget(payload);
                this.initQueue(payload);
                this.initEventArgs(payload);
                this.initModules(payload);
                this.handle(payload);
            },
            initPayload:function (payload) {
                payload.event = this._event;
                payload.type = this._type;
                payload.map = this._map;
            },
            initTarget:function (payload) {
                if (!payload.ignore) {
                    var event = payload.event;
                    payload.target = event.target || event.srcElement || document;
                }
            },
            initQueue:function (payload) {
                if (!payload.ignore) {
                    var target = payload.target;
                    var map = payload.map;
                    var queue = payload.queue = [];
                    for (var parent = target; dom.isElement(parent); parent = parent.parentNode) {
                        var entry = map[getHash(parent)];
                        if (entry) {
                            queue.push(entry);
                        }
                    }
                }
            },
            initEventArgs:function (payload) {
                if (!payload.ignore) {
                    var event = payload.event;
                    var eventClass = eventClasses[event.type] || fruit.Event;
                    var eventObj = new eventClass(event);
                    eventObj.type = payload.type;
                    payload.eventObj = eventObj;
                    payload.eventArgs = [eventObj];
                }
            },
            initModules:function (payload) {
                if (!payload.ignore) {
                    each(eventModules, function (module) {
                        if (module && module.active && module.init) {
                            module.init.call(null, payload);
                        }
                    });
                    return payload;
                }
            },
            handle:function (payload) {
                if (!payload.ignore) {
                    var eventArgs = payload.eventArgs;
                    var eventObj = payload.eventObj;
                    each(payload.queue, function (entry) {
                        each(entry, function (item) {
                            if (item) {
                                this.process(payload, item);
                                if (!eventObj.ignore && !eventObj.handled) {
                                    item.callback.apply(item.context || item.owner, eventArgs);
                                }
                            }
                        }, this);
                    }, this);
                }
            },
            process:function (payload, item) {
                each(item.meta, function (value, key) {
                    var module = eventModules[key];
                    if (module && module.active && module.process) {
                        module.process(payload, item, value);
                    }
                });
            }
        }
    });
    var MouseEnterLeaveEventHandler = fruit.define(EventHandler, {
        methods:{
            process:function (payload, item) {
                var eventObj = payload.eventObj;
                var related = eventObj.getRelatedTarget(),
                    target = eventObj.target,
                    element = item.owner;
                eventObj.ignore = !(related && !contains(element, related, true) && contains(element, target, true));
                this.inherited(item, eventObj);
            }
        }
    });
    var eventHandlers = {
        focus:new EventHandler('focus', null, true),
        blur:new EventHandler('blur', null, true),
        mouseenter:new MouseEnterLeaveEventHandler('mouseenter', 'mouseover'),
        mouseleave:new MouseEnterLeaveEventHandler('mouseleave', 'mouseout'),
        change:new EventHandler('change', null, true),
        input:new EventHandler('input', null, true)
    };
    var eventModules = {
        interval:{
            active:false,
            //TODO: change 'item' to ???
            process:function (payload, item, meta) {
                var eventObj = payload.eventObj;
                if (item.waiting) {
                    eventObj.ignore = true;
                }
                else {
                    item.waiting = true;
                    setTimeout(function () {
                        item.waiting = false;
                    }, meta);
                }
            }
        },
        //TODO: add callback function
        filter:{
            active:false,
            process:function (payload, item, meta) {
                var eventObj = payload.eventObj;
                var target = eventObj.target;
                var tag = meta.tag;
                var cls = meta.cls;
                if (typeof meta == 'function') {
                    return meta(item, eventObj);
                }
                if (tag && target.tagName.toLowerCase() != tag) {
                    eventObj.ignore = true;
                }
                if (cls) {
                    var className = target.className + ' ';
                    if (className.indexOf(cls) < 0) {
                        eventObj.ignore = true;
                    }
                }
            }
        }
    };
    var eventClasses = {
        'dbclick':fruit.MouseEvent,
        'click':fruit.MouseEvent,
        'mousedown':fruit.MouseEvent,
        'mouseup':fruit.MouseEvent,
        'mouseover':fruit.MouseEvent,
        'mousemove':fruit.MouseEvent,
        'mouseout':fruit.MouseEvent,
        'mousewheel':fruit.MouseEvent,
        'dragstart':fruit.MouseEvent,
        'drag':fruit.MouseEvent,
        'dragend':fruit.MouseEvent,
        'keyup':fruit.KeyboardEvent,
        'keydown':fruit.KeyboardEvent,
        'focus':fruit.FocusEvent,
        'focusin':fruit.FocusEvent,
        'blur':fruit.FocusEvent,
        'focusout':fruit.FocusEvent
    };
    var eventEngine = fruit.EventEngine = {
        ready:function (fn) {
            return $(document).ready(fn);
        },
        addEventListener:(function () {
            if (document.addEventListener) {
                return function (el, type, fn, capture) {
                    el.addEventListener(type, fn, capture || false);
                };
            } else {
                return function (el, type, fn) {
                    el.attachEvent("on" + type, fn);
                };
            }
        })(),
        removeEventListener:(function () {
            if (document.removeEventListener) {
                return function (el, type, fn) {
                    el.removeEventListener(type, fn);
                };
            } else {
                return function (el, type, fn) {
                    el.detachEvent("on" + type, fn);
                };
            }
        })(),
        hasEvent:function (inElement, inEventType) {
            return ('on' + inEventType) in inElement;
        },
        upon:function (owner, type, callback, context, meta) {
            var handler = eventHandlers[type];
            if (!handler) {
                handler = new EventHandler(type);
                this.registerEventHandler(type, handler);
            }
            handler.upon(owner, callback, context, meta);
            handler.activate();
        },
        on:function (owner, type, callback, context, meta) {
            var handler = eventHandlers[type];
            if (!handler) {
                handler = new EventHandler(type);
                this.registerEventHandler(type, handler);
            }
            handler.on(owner, callback, context, meta);
            handler.activate();
        },
        off:function (owner, type, callback, context) {
            var handler = eventHandlers[type];
            if (handler) {
                handler.off(owner, callback, context);
            }
        },
        activateModule:function (name) {
            var module = eventModules[name];
            if (module) {
                module.active = true;
            }
        },
        deactivateModule:function (name) {
            var module = eventModules[name];
            if (module) {
                module.active = false;
            }
        },
        registerEventModule:function (name, eventModule) {
            eventModules[name] = eventModule;
        },
        registerEventHandler:function (type, eventHandler) {
            if (eventHandlers[type]) {
                eventHandlers[type].deactivate();
            }
            eventHandlers[type] = eventHandler;
        },
        registerEventClass:function (type, eventClass) {
            eventClasses[type] = eventClass;
        }
    };
    fruit.eventEngine = eventEngine;
    fruit.ready = eventEngine.ready;
}(fruit, fruit.dom, fruit.util, fruit.support, window, document, undefined));